package com.easyadmin.interceptor;

import com.easyadmin.annotation.GlobalInterceptor;
import com.easyadmin.annotation.VerifyParam;
import com.easyadmin.config.AdminConfig;
import com.easyadmin.entity.constants.Constants;
import com.easyadmin.entity.dto.SessionUserDto;
import com.easyadmin.entity.enums.*;
import com.easyadmin.entity.po.SysOperationLog;
import com.easyadmin.entity.vo.AjaxResponseVO;
import com.easyadmin.exception.BusinessException;
import com.easyadmin.service.SysOperationLogService;
import com.easyadmin.utils.StringTools;
import com.easyadmin.utils.VerifyUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
@Aspect
public class OperationInterceptor {

    private static Logger logger = LoggerFactory.getLogger(OperationInterceptor.class);

    @Resource
    private SysOperationLogService sysOperationLogService;

    @Resource
    private AdminConfig adminConfig;

    private static final String TYPE_STRING = "java.lang.String";
    private static final String TYPE_INTEGER = "java.lang.Integer";
    private static final String TYPE_LONG = "java.lang.Long";

    @Pointcut("@annotation(com.easyadmin.annotation.GlobalInterceptor)")

    private void opMethods() {
    }

    @Around("opMethods()")
    public Object doMethod(ProceedingJoinPoint point) throws BusinessException {
        Object obj = null;
        try {
            /**
             * 获取登录信息
             */
            SessionUserDto sessionUserDto = getSessionUser();
            Object target = point.getTarget();
            Object[] arguments = point.getArgs();
            String method = point.getSignature().getName();
            Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
            Method m = target.getClass().getMethod(method, parameterTypes);
            GlobalInterceptor interceptor = m.getAnnotation(GlobalInterceptor.class);
            if (null == interceptor) {
                return obj;
            }

            if (adminConfig.getDemoShow() != null && adminConfig.getDemoShow()) {
                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                String[] allowPermissionCode = new String[]{
                        PermissionCodeEnum.NO_PERMISSION.getCode(),
                        PermissionCodeEnum.HOME.getCode(),
                        PermissionCodeEnum.SETTINGS.getCode(),
                        PermissionCodeEnum.SETTINGS_MENU.getCode(),
                        PermissionCodeEnum.SETTINGS_ACCOUNT.getCode(),
                        PermissionCodeEnum.SETTINGS_DIC.getCode(),
                        PermissionCodeEnum.SETTINGS_DIC_EDIT.getCode(),
                        PermissionCodeEnum.SETTINGS_DIC_DEL.getCode(),
                        PermissionCodeEnum.SETTINGS_ROLE.getCode(),
                        PermissionCodeEnum.SETTINGS_OPERLOG.getCode(),
                        PermissionCodeEnum.TASK_LIST.getCode()
                };
                if (!ArrayUtils.contains(allowPermissionCode, interceptor.permissionCode().getCode())) {
                    throw new BusinessException("演示环境不支持该操作");
                }
            }

            /**
             * 校验权限
             */
            if (sessionUserDto != null) {
                validatePermission(interceptor, sessionUserDto);
            }

            /**
             * 校验参数
             */
            validateParams(interceptor, m, arguments);

            /**
             * 获取描述信息，这里在执行方法之前组织好描述信息，当参数是传值引用时 执行具体方法后，会改变原始参数对象值
             */
            String description = getDescription(arguments, interceptor.opDescription());
            /**
             *  执行操作
             */
            obj = point.proceed();

            /**
             * 记录日志
             */
            wirteLog(obj, description, interceptor, sessionUserDto);

        } catch (BusinessException e) {
            logger.error("全局拦截器异常", e);
            throw e;
        } catch (Exception e) {
            logger.error("全局拦截器异常", e);
        } catch (Throwable e) {
            logger.error("全局拦截器异常", e);
        }
        return obj;
    }

    private SessionUserDto getSessionUser() throws BusinessException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        String requestUrl = request.getRequestURI();
        SessionUserDto sessionUser = (SessionUserDto) session.getAttribute(Constants.ADMIN_SESSION_KEY);
        Boolean needLogin = true;
        if (requestUrl.indexOf("checkCode") != -1 || requestUrl.indexOf("login") != -1) {
            needLogin = false;
        }
        if (null == sessionUser && needLogin) {
            throw new BusinessException(ResponseCodeEnum.CODE_901);
        }
        return sessionUser;
    }


    /**
     * @Description: 校验操作权限
     * @auther: 多多洛
     * @date: 11:36 2020/12/6
     * @param: [interceptor, sessionUser]
     * @return: void
     */
    private void validatePermission(GlobalInterceptor interceptor, SessionUserDto sessionUser) throws BusinessException {
        PermissionCodeEnum permissionCode = interceptor.permissionCode();
        /**
         * 无需校验权限的，直接设置 no_permission
         */
        if (PermissionCodeEnum.NO_PERMISSION == permissionCode) {
            return;
        }
        List<String> permisinCodes = sessionUser.getPermissionCodeList();
        Boolean havePermission = permisinCodes.contains(permissionCode.getCode());
        if (!havePermission) {
            throw new BusinessException(ResponseCodeEnum.CODE_902);
        }
    }

    /**
     * @Description:
     * @auther: 参数校验，后端参数校验 防小人不妨君子，前端已经做了参数校验，绕过前端，后端校验，一律返回 请求参数不正确。
     * @date: 15:10 2020/12/6
     * @param: [interceptor, m, arguments]
     * @return: void
     */
    private void validateParams(GlobalInterceptor interceptor, Method m, Object[] arguments) throws BusinessException {
        Parameter[] parameters = m.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Object value = arguments[i];
            VerifyParam verifyParam = parameter.getAnnotation(VerifyParam.class);
            if (verifyParam == null) {
                continue;
            }
            /**
             * 校验空
             */
            Boolean isEmpty = value == null || StringTools.isEmpty(value.toString());
            if (verifyParam.required() && isEmpty) {
                throw new BusinessException(ResponseCodeEnum.CODE_600);
            }

            //基本数据类型
            if (TYPE_STRING.equals(parameter.getParameterizedType().getTypeName()) || TYPE_LONG.equals(parameter.getParameterizedType().getTypeName()) || TYPE_INTEGER.equals(parameter.getParameterizedType().getTypeName())) {
                checkValue(value, verifyParam);
                //如果传递的是对象
            } else {
                try {
                    Class classz = Class.forName(parameter.getParameterizedType().getTypeName());
                    Field[] fields = classz.getDeclaredFields();
                    for (Field field : fields) {
                        VerifyParam fieldVerifyParam = field.getAnnotation(VerifyParam.class);
                        if (fieldVerifyParam == null) {
                            continue;
                        }
                        field.setAccessible(true);
                        value = field.get(arguments[i]);
                        checkValue(value, fieldVerifyParam);
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 校验参数
     *
     * @param value
     * @param verifyParam
     * @throws BusinessException
     */
    private void checkValue(Object value, VerifyParam verifyParam) throws BusinessException {
        Boolean isEmpty = value == null || StringTools.isEmpty(value.toString());
        Integer length = value == null ? 0 : value.toString().length();
        /**
         * 校验长度
         */
        if (!isEmpty && (verifyParam.max() != -1 && verifyParam.max() < length || verifyParam.min() != -1 && verifyParam.min() > length)) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        /**
         * 校验正则
         */
        if (!isEmpty && !StringTools.isEmpty(verifyParam.regex().getRegex()) && !VerifyUtils.verify(verifyParam.regex(), String.valueOf(value))) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
    }

    /**
     * @Description: 记录日志
     * @auther: 多多洛
     * @date: 11:34 2020/12/6
     * @param: [obj, arguments, interceptor, sessionUser]
     * @return: void
     */
    private void wirteLog(Object obj, String description, GlobalInterceptor interceptor, SessionUserDto sessionUser) {
        try {

            if (interceptor.opObject() == OperModuleEnum.NO_OBJECT) {
                return;
            }
            OpResultEnum opResult = OpResultEnum.FAIL;
            //判断是否操作成功
            String errorMsg = "";
            if (obj instanceof AjaxResponseVO) {
                AjaxResponseVO result = (AjaxResponseVO) obj;
                if (ResponseStatusEnum.SUCCESS.getStatus().equals(result.getStatus())) {
                    opResult = OpResultEnum.SUCCESS;
                } else {
                    errorMsg = result.getInfo();
                }
            }
            SysOperationLog log = new SysOperationLog();
            log.setUserId(sessionUser == null ? null : sessionUser.getUserId());
            log.setUserName(sessionUser == null ? null : sessionUser.getUserName());
            log.setOpObject(interceptor.opObject().getType());
            log.setOpDescription(description);
            log.setCreateTime(new Date());
            log.setOpResult(opResult.getType());
            log.setErrorMsg(errorMsg);
            log.setIp(getClientIpAddress());
            sysOperationLogService.saveSysOperationLog(log);
        } catch (Exception e) {
            logger.error("记录日志失败", e);
        }
    }

    private String getDescription(Object[] arguments, String opDescription) {

        Map<String, Object> namedParams = new HashMap<>();
        for (int i = 0; i < arguments.length; i++) {
            Object paramObj = arguments[i];
            if (paramObj instanceof HttpServletResponse || paramObj instanceof HttpSession) {
                continue;
            }
            String param = arguments[i] == null ? null : arguments[i].toString();//JsonUtils.convertObj2Json(arguments[i]);
            param = StringTools.isEmpty(param) ? "空" : param;
            namedParams.put("param" + i, param);

        }
        String description = formateString(opDescription, namedParams);
        try {
            description = URLDecoder.decode(description, "utf-8");
        } catch (UnsupportedEncodingException e) {
            logger.error("Encoding失败", e);
        }
        return description;
    }

    private String formateString(String sourceStr, Map<String, Object> namedParams) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariables(namedParams);
        ExpressionParser parser = new SpelExpressionParser();
        String resut = parser.parseExpression(sourceStr, new TemplateParserContext()).getValue(context, String.class);
        return resut;
    }

    public static String getClientIpAddress() {
        final String[] HEADERS_TO_TRY = {
                "X-Forwarded-For",
                "Proxy-Client-IP",
                "WL-Proxy-Client-IP",
                "HTTP_X_FORWARDED_FOR",
                "HTTP_X_FORWARDED",
                "HTTP_X_CLUSTER_CLIENT_IP",
                "HTTP_CLIENT_IP",
                "HTTP_FORWARDED_FOR",
                "HTTP_FORWARDED",
                "HTTP_VIA",
                "REMOTE_ADDR",
                "X-Real-IP"};
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        for (String header : HEADERS_TO_TRY) {
            String ip = request.getHeader(header);
            if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
                return ip;
            }
        }
        return request.getRemoteAddr();
    }
}